﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace TypeBuilderFx.Core.Emitters
{
    /// <summary>Represents the semantic of a method.</summary>
    /// <remarks>Creates an abstraction layer between the reflected method and its use to map to a sproc.</remarks>
    internal class SprocMethodMapping
    {
        #region Inner Types
        /// <summary>
        /// Mapping between a method reflected parameters (and return value) and a sproc parameters (and returned
        /// result sets).
        /// </summary>
        internal abstract class SprocParameterMapping : IVariable
        {
            private readonly ILGeneratorExtender _ilGenerator;
            private readonly string _name;
            private readonly Type _type;
            private readonly bool _isIn;
            private readonly bool _isOut;

            /// <summary>Construct a parameter mapping.</summary>
            /// <param name="ilGenerator"></param>
            /// <param name="name"></param>
            /// <param name="type"></param>
            /// <param name="isIn"></param>
            /// <param name="isOut"></param>
            protected SprocParameterMapping(ILGeneratorExtender ilGenerator, string name, Type type, bool isIn, bool isOut)
            {
                _ilGenerator = ilGenerator;
                _name = name;
                _type = type;
                _isIn = isIn;
                _isOut = isOut;
            }

            #region IVariable
            /// <summary>See <see cref="IVariable.Type"/>.</summary>
            public Type Type
            {
                get { return _type; }
            }

            /// <summary>See <see cref="IVariable.StoreIn"/>.</summary>
            public abstract void StoreIn();

            /// <summary>See <see cref="IVariable.StoreOut"/>.</summary>
            public abstract void StackOut();
            #endregion

            /// <summary>Name of the parameter.</summary>
            public string Name
            {
                get { return _name; }
            }

            /// <summary>Is the parameter able to get data in.</summary>
            public bool IsIn
            {
                get { return _isIn; }
            }

            /// <summary>Is the parameter able to get data out.</summary>
            public bool IsOut
            {
                get { return _isOut; }
            }

            /// <summary>Returns the IL-Generator.</summary>
            protected ILGeneratorExtender IlGenerator
            {
                get { return _ilGenerator; }
            }
        }

        private class SprocParameterMappingParameter : SprocParameterMapping
        {
            private readonly int _parameterIndex;

            /// <summary>Construct a parameter.</summary>
            /// <param name="ilGenerator"></param>
            /// <param name="name"></param>
            /// <param name="type"></param>
            /// <param name="isIn"></param>
            /// <param name="isOut"></param>
            /// <param name="parameterIndex"></param>
            public SprocParameterMappingParameter(
                ILGeneratorExtender ilGenerator, string name, Type type, bool isIn, bool isOut, int parameterIndex)
                : base(ilGenerator, name, type, isIn, isOut)
            {
                _parameterIndex = parameterIndex;
            }

            /// <summary>Stores the stack value in the parameter.</summary>
            public override void StoreIn()
            {
                throw new NotImplementedException();
            }

            /// <summary>Stacks the parameter value.</summary>
            public override void StackOut()
            {
                IlGenerator.StackMethodParameter(_parameterIndex);
            }
        }

        private class SprocParameterMappingReturn : SprocParameterMapping
        {
            private readonly LocalBuilder _returnVar;

            /// <summary>Construct a return value.</summary>
            /// <param name="ilGenerator"></param>
            /// <param name="type"></param>
            public SprocParameterMappingReturn(ILGeneratorExtender ilGenerator, Type type)
                : base(ilGenerator, "#return#", type, false, true)
            {
                _returnVar = ilGenerator.DefineVariable(type);
            }

            /// <summary>Stores the stack value in the return variable.</summary>
            public override void StoreIn()
            {
                IlGenerator.StoreStackInVariable(_returnVar);
            }

            /// <summary>Can't stack the return value.</summary>
            public override void StackOut()
            {
                IlGenerator.StackVariable(_returnVar);
            }

            /// <summary>Returns the return variable.</summary>
            public void Returns()
            {
                StackOut();
                IlGenerator.ReturnStack();
            }
        }
        #endregion

        private readonly MethodInfo _method;
        private readonly SprocParameterMapping[] _inputParameters;
        private readonly SprocParameterMapping[] _outputParameters;
        private readonly SprocParameterMappingReturn _returnParameter;

        /// <summary>Construct a method mapping.</summary>
        /// <param name="method"></param>
        /// <param name="ilGenerator"></param>
        public SprocMethodMapping(MethodInfo method, ILGeneratorExtender ilGenerator)
        {
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }
            if (ilGenerator == null)
            {
                throw new ArgumentNullException("ilGenerator");
            }
            _method = method;
            _returnParameter = GetReturnParameter(method.ReturnType, ilGenerator);
            _inputParameters = GetInputParameters(method, ilGenerator);
            _outputParameters = GetOutputParameters(method, ilGenerator, _returnParameter);
        }

        /// <summary>Returns the method name.</summary>
        public string Name
        {
            get { return _method.Name; }
        }

        /// <summary>Returns the input parameters:  the parameters taking data in to the sproc.</summary>
        public SprocParameterMapping[] InputParameters
        {
            get { return _inputParameters; }
        }

        /// <summary>Returns the output parameters:  the parameters taking data out of the sproc.</summary>
        /// <remarks>Return parameter, if there is any, is at the end.</remarks>
        public SprocParameterMapping[] OutputParameters
        {
            get { return _outputParameters; }
        }

        /// <summary>Returns the return parameter:  output parameter associated with the return value of the method.</summary>
        public SprocParameterMapping ReturnParameter
        {
            get { return _returnParameter; }
        }

        /// <summary>Returns the return parameter if there is one.</summary>
        public void Returns()
        {
            if (_returnParameter != null)
            {
                _returnParameter.Returns();
            }
        }

        private static SprocParameterMapping[] GetInputParameters(MethodInfo method, ILGeneratorExtender ilGenerator)
        {
            List<SprocParameterMapping> parameterList = new List<SprocParameterMapping>();

            for (int index = 0; index != method.GetParameters().Length; ++index)
            {
                ParameterInfo info = method.GetParameters()[index];

                if (info.IsOut && !info.IsIn)
                {
                    //  Is an output param
                }
                else
                {
                    parameterList.Add(new SprocParameterMappingParameter(
                        ilGenerator, info.Name, info.ParameterType, info.IsIn, info.IsOut, index));
                }
            }

            return parameterList.ToArray();
        }

        private static SprocParameterMapping[] GetOutputParameters(
            MethodInfo method,
            ILGeneratorExtender ilGenerator,
            SprocParameterMapping returnParameter)
        {
            List<SprocParameterMapping> parameterList = new List<SprocParameterMapping>();

            for (int index = 0; index != method.GetParameters().Length; ++index)
            {
                ParameterInfo info = method.GetParameters()[index];

                if (info.IsOut)
                {
                    parameterList.Add(new SprocParameterMappingParameter(
                        ilGenerator, info.Name, info.ParameterType, info.IsIn, true, index));
                }
                else
                {
                    //  Is an input parameter
                }
            }
            if (returnParameter != null)
            {
                parameterList.Add(returnParameter);
            }

            return parameterList.ToArray();
        }

        private static SprocParameterMappingReturn GetReturnParameter(Type returnType, ILGeneratorExtender ilGenerator)
        {
            if (returnType == typeof(void))
            {
                return null;
            }
            else
            {
                return new SprocParameterMappingReturn(ilGenerator, returnType);
            }
        }
    }
}